#include "process.h"
#include "conn_event.h"
#include "cfg.h"
#include "version.h"
#include "transfer.h"

/* process connev received data */
int32_t connev_process(struct connev_t* connev)
{
	assert(connev && connev->ev);
	int proc_ret, len;

CONNEV_PROC:
	/* get read buffer len */
	len = connbuffer_read_len(connev->in_buffer);

	/* not enough for head */
	if(len < sizeof(struct conn_head_t))
		return 0;

	/* get head */
	struct conn_head_t head;
	deserialize_conn_head(&head, connbuffer_read_buffer(connev->in_buffer), sizeof(struct conn_head_t));
	
	switch(head.cmd)
	{
		case ID_C2CONN_SYN:
			LOG_TRACE("connev from %s read SYN", connev_peer_addr(connev));
			proc_ret = connev_proc_syn(connev, &head);
			break;

		case ID_C2CONN_ACK:
			LOG_TRACE("connev from %s read ACK", connev_peer_addr(connev));
			proc_ret = connev_proc_ack(connev, &head);
			break;

		case ID_C2CONN_NTF_PKG:
			LOG_TRACE("connev from %s read NTF_PKG", connev_peer_addr(connev));
			proc_ret = connev_proc_pkg(connev, &head);
			break;

		case ID_C2CONN_NTF_HEARTBEAT:
			LOG_TRACE("connev from %s read HEARTBEAT", connev_peer_addr(connev));
			proc_ret = connev_proc_heartbeat(connev, &head);
			break;

		default:
			LOG_ERROR("connev from %s read unrecognized cmd=%d, destruction", 
				connev_peer_addr(connev), head.cmd);
			connev_destruct(connev, connev_notify_none);
			return -1;
	}

	if(0 == proc_ret) 
		goto CONNEV_PROC;
	else
		return proc_ret;
}

/*
*	client sync
*	return 0, process  success
*	return >0, pkg not full
*	return <0, process fail, destruct connection
*/
int32_t connev_proc_syn(struct connev_t* connev, struct conn_head_t* client_head)
{
	assert(connev && client_head);

	/* read out data */
	connbuffer_read_nocopy(connev->in_buffer, sizeof(struct conn_head_t));

	/* check version */
	int32_t version = client_head->flag;
	if(version != evconn_version())
	{
		LOG_ERROR("connev from %s client version=%d, evconnsvrd version=%d, destruct connection",
			connev_peer_addr(connev), version, evconn_version());
		connev_destruct(connev, connev_notify_client_version_fail);
		return -1;
	}
	
	/* check status */
	if(connev->status != connev_status_wait_syn)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_wait_syn);
		goto SYN_FAIL;
	}

	/* generate encrypt key */
	connev->encrypt_key = 2013; // for test, use a fix data , replace rand();

	/* write CONN2C_ACK */
	int ret = transfer_conn2c(ID_CONN2C_ACK, connev->encrypt_key, connev);
	if(ret < 0)
	{
		LOG_ERROR("connev from %s write fail, destruction", connev_peer_addr(connev));
		goto SYN_FAIL;
	}

	/* update status */
	connev->status = connev_status_wait_ack;
	return 0;

SYN_FAIL:
	connev_destruct(connev, connev_notify_none);
	return -1;
}

/*
*	client ack, check encrypt key & notify to back-server
*	return 0, process  success
*	return >0, pkg not full
*	return <0, process fail, destruct connection
*/
int32_t connev_proc_ack(struct connev_t* connev, struct conn_head_t* client_head)
{
	assert(connev && client_head);
	int32_t ret, client_encrypt_key;
	
	/* read out data */
	connbuffer_read_nocopy(connev->in_buffer, sizeof(struct conn_head_t));

	/* check status */
	if(connev->status != connev_status_wait_ack)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_wait_ack);
		goto ACK_FAIL;
	}

	/* check encrypt key */
	client_encrypt_key = client_head->flag;
	if(connev->encrypt_key != client_encrypt_key)
	{
		LOG_ERROR("connev from %s get encrypt key=%d, but should be %d fail, destruction",
			connev_peer_addr(connev), client_encrypt_key, connev->encrypt_key);
		goto ACK_FAIL;
	}

	/* waiting list not empty, direct put in queue */
	if(gCtx->waiting_conn_count > 0)
	{
		LOG_DEBUG("back-server busy, add connev from %s to waiting list", connev_peer_addr(connev));

		/* add to waiting list */
		list_add_tail(&connev->waiting, &gCtx->waiting_list);

		/* update statics */
		gCtx->waiting_conn_count ++;

		/* res ID_CONN2C_NTF_IN_QUEUE */
		int ret = transfer_conn2c(ID_CONN2C_NTF_IN_QUEUE, gCtx->waiting_conn_count, connev);
		if(ret < 0)
		{
			LOG_ERROR("connev from %s write fail, destruction", connev_peer_addr(connev));
			list_del(&connev->waiting);
			
			/* update statics */
			gCtx->waiting_conn_count --;
			goto ACK_FAIL;
		}

		/* update status */
		connev->status = connev_status_in_queue;
	}

	/* request connection to back-server */
	else
	{
		/* send CONN2S_REQ_CONNECT */
		ret = transfer_conn2s(ID_CONN2S_REQ_CONNECT, connev->address, gCtx->lsi, gCtx->cfg->lsi_server_ip);
		if(ret < 0)
		{
			LOG_FATAL("lsi write fail, ret=%d, destruction connection", ret);
			goto ACK_FAIL;
		}
		
		/* update status */
		connev->status = connev_status_wait_server_res;
	}
	
	return 0;

ACK_FAIL:
	connev_destruct(connev, connev_notify_none);
	return -1;
}

/*
* pop one in waiting queue
*/
int32_t connev_proc_pop_queue()
{
	struct list_head* pop = gCtx->waiting_list.next;
	struct connev_t* connev = list_entry(pop, struct connev_t, waiting);
	assert(connev && connev->status == connev_status_in_queue);

	/* remove from queue */
	list_del(&connev->waiting);
	gCtx->waiting_conn_count --;

	/* send CONN2S_REQ_CONNECT  */
	int ret = transfer_conn2s(ID_CONN2S_REQ_CONNECT, connev->address, gCtx->lsi, gCtx->cfg->lsi_server_ip);
	if(ret < 0)
		connev_destruct(connev, connev_notify_none);
	else
		/* update status */
		connev->status = connev_status_wait_server_res;

	/* notify for other in-queue clients */
	if(gCtx->waiting_conn_count > 0)
	{
		struct connev_t* queue_connev;
		int index = 1;
		list_for_each_entry(queue_connev, &gCtx->waiting_list, waiting)
		{
			transfer_conn2c(ID_CONN2C_NTF_IN_QUEUE, index++, queue_connev);
		}	
	}

	return 0;
}


/*
*	client send pkg, foward to back-server
*	return 0, process  success
*	return >0, pkg not full
*	return <0, process fail, destruct connection
*/
int32_t connev_proc_pkg(struct connev_t* connev, struct conn_head_t* client_head)
{
	assert(connev && client_head);
	int32_t len, crc, ret;
	char *pkg_buffer;
	
	/* check status */
	if(connev->status != connev_status_connected)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_connected);
		connev_destruct(connev, connev_notify_server);
		return -1;
	}

	/* check receive full data */
	len = connbuffer_read_len(connev->in_buffer);
	if(len < sizeof(struct conn_head_t) + client_head->pkg_len)
	{
		return 1;
	}

	/* check pkg buffer crc */
	pkg_buffer = connbuffer_read_buffer(connev->in_buffer) + sizeof(struct conn_head_t);
	crc = crc32(pkg_buffer, client_head->pkg_len);
	if(crc != client_head->pkg_crc)
	{
		LOG_ERROR("connev from %s get crc=%d, but calced=%d, ignore",
			connev_peer_addr(connev), client_head->pkg_crc, crc);
		/* read out data */
		connbuffer_read_nocopy(connev->in_buffer, sizeof(struct conn_head_t) + client_head->pkg_len);
		return 0;
	}

	/* descrypt and send */
	ret = transfer_conn2s_pkg(ID_CONN2S_NTF_PKG, connev->address,
		pkg_buffer, client_head->pkg_len, connev->encrypt_key,
		gCtx->lsi, gCtx->cfg->lsi_server_ip);
	if(ret < 0)
	{
		connev_destruct(connev, connev_notify_server);
		return -1;
	}

	/* read out data */
	connbuffer_read_nocopy(connev->in_buffer, sizeof(struct conn_head_t) + client_head->pkg_len);
	return 0;
}

/*
*	client heartbeat
*	return 0, process  success
*	return >0, pkg not full
*	return <0, process fail, destruct connection
*/
int32_t connev_proc_heartbeat(struct connev_t* connev, struct conn_head_t* client_head)
{
	assert(connev && client_head);

	/* read out data */
	connbuffer_read_nocopy(connev->in_buffer, sizeof(struct conn_head_t));

	LOG_TRACE("connev from %s heartbeat", connev_peer_addr(connev));
	return 0;
}

/* 
* process lsi read buffer 
* return 0: process success
* return >: no read data to process
*/
int32_t lsi_process()
{
	static char recv_buff[EVCONN_CONN_BUFFER_SIZE];
	size_t recv_buff_len = sizeof(recv_buff);
	int ret = lsi_recv(gCtx->lsi, gCtx->cfg->lsi_server_ip, recv_buff, &recv_buff_len);
	if(0 == ret)
	{
		assert(recv_buff_len >= sizeof(struct conn_head_t));
		struct server_head_t head;
		deserialize_server_head(&head, recv_buff, sizeof(head));

		switch(head.cmd)
		{
			case ID_S2CONN_RES_CONNECT:
				assert(recv_buff_len == sizeof(head));
				LOG_TRACE("LSI read  CONNECT_RES address=%d", head.address);
				return lsi_proc_res(&head);

			case ID_S2CONN_NTF_PKG:
				LOG_TRACE("LSI read PKG_NTF address=%d", head.address);
				return lsi_proc_pkg(&head, recv_buff + sizeof(head), recv_buff_len - sizeof(head));

			case ID_S2CONN_NTF_DISCONN:
				assert(recv_buff_len == sizeof(head));
				LOG_TRACE("LSI read  DISCONN_NTF address=%d", head.address);
				return lsi_proc_disconn(&head);

			default:
				LOG_FATAL("LSI read invalid cmd=%d", head.cmd);
				assert(0);
		}
	}
	else if(ret != LSI_ChannEmpty)
	{
		LOG_FATAL("LSI recv fail, ret=%d", ret);
		assert(0);
	}

	return 1;
}

/*
* back-server response connect
*/
int32_t lsi_proc_res(struct server_head_t* head)
{
	assert(head);

	/* find dest connection */
	connev_t temp;
	temp.address = head->address;
	struct connev_t* connev = hash_find(gCtx->connev_table, &temp);
	if(!connev)
	{
		LOG_ERROR("connev with address=%d not found, ignore", head->address);
		return 0;
	}

	/* check status */
	if(connev->status != connev_status_wait_server_res)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_wait_server_res);
		goto CONNECT_FAIL;
	}

	/* check server res connect result */
	int result = head->pkg_crc;
	/* success */
	if(0 == result)
	{
		/* CONN2C_RES_CONNECT */
		int ret = transfer_conn2c(ID_CONN2C_NTF_CONNECT, 0, connev);
		if(ret < 0)
		{
			LOG_ERROR("connev from %s write fail, destruction", connev_peer_addr(connev));
			goto CONNECT_FAIL;
		}

		/* update status */
		connev->status = connev_status_connected;

		/* update statics */
		gCtx->connected_conn_count ++;

		LOG_DEBUG("connev from %s build handshake success", connev_peer_addr(connev));
		return 0;
	}

	/* fail, add to waiting list */
	else
	{
		LOG_DEBUG("back-server res fail, add connev from %s to waiting list", connev_peer_addr(connev));

		/* add to waiting list */
		list_add_tail(&connev->waiting, &gCtx->waiting_list);

		/* update statics */
		gCtx->waiting_conn_count ++;

		/* res ID_CONN2C_NTF_IN_QUEUE */
		int ret = transfer_conn2c(ID_CONN2C_NTF_IN_QUEUE, gCtx->waiting_conn_count, connev);
		if(ret < 0)
		{
			LOG_ERROR("connev from %s write fail, destruction", connev_peer_addr(connev));
			list_del(&connev->waiting);

			/* update statics */
			gCtx->waiting_conn_count --;
			
			goto CONNECT_FAIL;
		}

		/* update status */
		connev->status = connev_status_in_queue;
		return 0;
	}

CONNECT_FAIL:
	connev_destruct(connev, connev_notify_server);
	return 0;
}

/*
* back-server notify pkg
*/
int32_t lsi_proc_pkg(struct server_head_t* head, const char* buffer, int buffer_len)
{
	assert(head);

	/* find dest connection */
	connev_t temp;
	temp.address = head->address;
	struct connev_t* connev = hash_find(gCtx->connev_table, &temp);
	if(!connev)
	{
		LOG_ERROR("connev with address=%d not found, ignore", head->address);
		return 0;
	}

	/* check status */
	if(connev->status != connev_status_connected)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_connected);
		goto PKG_FAIL;
	}

	/* check pkg len & crc */
	assert(head->pkg_len == buffer_len);
	int32_t crc = crc32(buffer, buffer_len);
	assert(head->pkg_crc == crc);

	/* CONN2C_NTF_PKG */
	int ret = transfer_conn2c_pkg(ID_CONN2C_NTF_PKG, 0, buffer, buffer_len, connev->encrypt_key, connev);
	if(ret < 0)
	{
		LOG_ERROR("connev from %s write fail, destruction", connev_peer_addr(connev));
		goto PKG_FAIL;
	}

	return 0;

PKG_FAIL:
	connev_destruct(connev, connev_notify_server);
	return 0;
}

/*
* back-server disconnect
*/
int32_t lsi_proc_disconn(struct server_head_t* head)
{
	assert(head);

	/* find dest connection */
	connev_t temp;
	temp.address = head->address;
	struct connev_t* connev = hash_find(gCtx->connev_table, &temp);
	if(!connev)
	{
		LOG_ERROR("connev with address=%d not found, ignore", head->address);
		return 0;
	}

	/* check status */
	if(connev->status != connev_status_connected)
	{
		LOG_ERROR("connev from %s status=%d, expected status=%d fail, destruct connection",
			connev_peer_addr(connev), connev->status, connev_status_connected);
		goto DISCONN_FAIL;
	}

	/* reused as reason */
	int reason = head->pkg_crc;

	/* complete and disconnect */
	LOG_DEBUG("connev from %s, server disconnect reason=%d",
		connev_peer_addr(connev), reason);
	connev_destruct(connev, connev_notify_client_server_disconn);
	return 0;

DISCONN_FAIL:
	connev_destruct(connev, connev_notify_none);
	return 0;
}


